﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;
using DevExpress.XtraEditors;

namespace DevTools.Common.Components
{
    public static class ControlReflector
    {
        public const string Prefix = "prp";

        public static List<T> GetChildren<T>(Control container) where T : class
        {
            List<T> controls = new List<T>();

            GetChildrenCore(container, controls);

            return controls;
        }

        private static void GetChildrenCore<T>(Control container, List<T> controls) where T : class
        {
            var fields = container.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic);

            foreach (var field in fields)
            {
                if (typeof(T).IsAssignableFrom(field.FieldType))
                    controls.Add((T)field.GetValue(container));

                else if (field.FieldType.IsSubclassOf(typeof(UserControl)))
                    GetChildrenCore((UserControl)field.GetValue(container), controls);
            }
        }

        public static string GetMisspelledEditControl(Control control, object @object)
        {
            var editControls = GetChildren<BaseEdit>(control);

            var objectProperties = @object.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();

            foreach (var editControl in editControls)
            {
                if (editControl.Name.StartsWith(Prefix) == false)
                    continue;

                bool exist = objectProperties.Any(x => x.Name == editControl.Name.Substring(3));

                if (exist == false)
                    return editControl.Name;
            }

            return null;
        }

        public static void ReadFromObject(Control control, object @object)
        {
            Action<object, PropertyInfo, BaseEdit> action = (o, objectProperty, editControl) =>
                                                            editControl.EditValue = objectProperty.GetValue(o, null);

            LoadOrSaveObject(control, @object, action, false);
        }

        public static void WriteToObject(Control control, object @object)
        {
            Action<object, PropertyInfo, BaseEdit> action = (o, objectProperty, editControl) =>
                                                            objectProperty.SetValue(o, editControl.EditValue, null);

            LoadOrSaveObject(control, @object, action, true);
        }

        private static void LoadOrSaveObject(Control control, object @object,
                                             Action<object, PropertyInfo, BaseEdit> action, bool toObject)
        {
            var allFields = control.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
            var editControls = allFields.Where(x => x.FieldType.IsSubclassOf(typeof(BaseEdit))).ToList();

            var objectProperties = @object.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();

            foreach (var editControl in editControls)
            {
                if (editControl.Name.StartsWith(Prefix) == false)
                    continue;

                var objectProperty = objectProperties.Find(x => x.Name == editControl.Name.Substring(3));

                if (objectProperty == null)
                    continue;

                BaseEdit baseEdit = (BaseEdit)editControl.GetValue(control);

                // SpinEdit인 경우에는 EditValue가 decimal 형이므로 별도 처리한다.
                if (baseEdit is SpinEdit)
                {
                    var objectValue = objectProperty.GetValue(@object, null);
                    if (objectValue is int)
                    {
                        SpinEdit spinEdit = (SpinEdit)baseEdit;

                        if (toObject)
                            objectProperty.SetValue(@object, Convert.ToInt32(spinEdit.EditValue), null);
                        else
                            spinEdit.EditValue = Convert.ToDecimal(objectProperty.GetValue(@object, null));
                    }
                    else
                    {
                        action(@object, objectProperty, baseEdit);
                    }
                }
                else
                {
                    action(@object, objectProperty, baseEdit);
                }
            }
        }
    }
}
